package jschool.homework.connection.pool;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

class SemaphoreConnectionPool implements ConnectionPool {

	private final BlockingQueue<PooledConnection> connectionPool;
	private final Semaphore semaphore;	
	private final String url;
	private volatile boolean closed;
	
	SemaphoreConnectionPool(String connectionUrl, int maxConnections) throws SQLException {
		this.url = connectionUrl;
		semaphore = new Semaphore(maxConnections);
		connectionPool = new LinkedBlockingQueue<>();
	}
	
	@Override
	public PooledConnection getConnection() throws InterruptedException, SQLException {
		return getConnection(Long.MAX_VALUE);
	}
	
	@Override
	public PooledConnection getConnection(long timeout) throws InterruptedException, SQLException {
		if (closed) {
			throw new IllegalStateException("Connection Pool is closed");
		}
		
		boolean aqcuired = semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS);
		if (!aqcuired) {
			return null;
		}
		
		PooledConnection conn = connectionPool.poll();
		if (conn != null) {
			conn.setActive(true);
			return conn;
		}
		
		return createNewPooledConnection();
	}
	
	@Override
	public void releaseConnection(PooledConnection connection) {
		if (this.closed) {
			return;
		}
		
		if (!connection.setActive(false)) {
			// Try to release connection that has already been released
			System.out.println("Connection is released or closed");
			return;
		}
		
		try {
			connection.rollback();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		connectionPool.offer(connection);
		semaphore.release();
	}
	
	@Override
	public void close() {
		closed = true;
		List<PooledConnection> connPool = new ArrayList<>();
		connectionPool.drainTo(connPool);
		for (PooledConnection conn : connPool) {
			try {
				conn.removeConnectionEventListener(this);
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	@Override
	public void connectionClosed() {
		if (closed) {
			return;
		}
		
		System.out.println("Connection has been closed");
		
		try {
			connectionPool.offer(createNewPooledConnection());
		} catch (SQLException e) {
			System.err.println("Can't create new pooled connection in place of closed");
			e.printStackTrace();
		}
	}
	
	private PooledConnection createNewPooledConnection() throws SQLException {
		PooledConnection conn = new PooledConnection(createConnection());
		conn.addConnectionEventListener(this);
		conn.setActive(true);
		semaphore.release();
		return conn;
		
	}
	
	protected Connection createConnection() throws SQLException {
		return DriverManager.getConnection(url);
	}
	
}
